
local ADDON, ns = ...
local InterruptHelper = CreateFrame("Frame", "InterruptHelperFrame")
ns.addon = InterruptHelper

local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
local AceDB = LibStub and LibStub("AceDB-3.0", true)

-- Presets (starter set)
local PRESET_DUNGEON = {
    [424322]=true,[424212]=true,[431152]=true,[432734]=true,[430185]=true,[429735]=true,
    [434173]=true,[433841]=true,[428023]=true,[428042]=true,[410982]=true,[411325]=true,
    [440912]=true,[440941]=true,[385310]=true,[373804]=true,[391136]=true,[384930]=true,[426256]=true
}
local PRESET_RAID = {
    [441361]=true,[442527]=true,[441873]=true,[442145]=true,[442819]=true,[443093]=true,[443276]=true,[443711]=true,
    [372315]=true,[374706]=true,[402583]=true,[401592]=true
}

local DEFAULTS = {
    profile = {
        enabled = true,
        sound = "RAID_WARNING",
        customPath = "",
        volume = "Master",
        volumeSlider = 1.0,
        useVolumeSlider = false,

        -- Text
        showText = true,
        text = "立即打断！",
        textColor = { r=1, g=0.2, b=0.2, a=1 },
        textAlpha = 1.0,
        textFont = "",
        textSize = 48,
        textOutline = "OUTLINE",
        bgAlpha = 0.2,
        textDuration = 2.0,
        testSticky = true,
        textPoint = { point="CENTER", rel="UIParent", relPoint="CENTER", x=0, y=120 },

        -- Filters
        filterMode = "IMPORTANT",
        instanceScope = "INSTANCES",
        dungeonDiff = { N=true, H=true, M=true, MPLUS=true },
        raidDiff    = { LFR=true, N=true, H=true, M=true },
        importantSpells = {},
        ignoreSpells = {},
        language = "AUTO",
        usePresetDungeon = true,
        usePresetRaid = true,

        -- Chat announcements (optional, default OFF)
        announceEnabled   = false,
        announceChannel   = "AUTO", -- AUTO, PARTY, RAID, INSTANCE_CHAT, SAY
        announceInWorld   = false,
        announceTemplate  = "注意：%mob%正在施放%spell% - 请立即打断！",
        announceThrottleS = 3.0,
        announceOnlyIfReady = false,

        -- Debug
        debug = false,
        debugAnnounce = false,
    }
}

local function DB() return (ns.db and ns.db.profile) or _G.InterruptHelperDB end

local SOUND_MAP = { RAID_WARNING = SOUNDKIT.RAID_WARNING, ALARM = SOUNDKIT.ALARM_CLOCK_WARNING_3, }

-- ===== Text frame =====
local textFrame = CreateFrame("Frame", "InterruptHelperTextFrame", UIParent, "BackdropTemplate")
textFrame:SetClampedToScreen(true)
textFrame:SetSize(600, 120)
textFrame:SetMovable(true)
textFrame:EnableMouse(true)
textFrame:RegisterForDrag("LeftButton")
textFrame:SetScript("OnDragStart", function(self) if not self.locked then self:StartMoving() end end)
textFrame:SetScript("OnDragStop", function(self)
    self:StopMovingOrSizing()
    local point, rel, relPoint, x, y = self:GetPoint()
    local db = DB()
    db.textPoint = { point=point, rel=rel and rel:GetName() or "UIParent", relPoint=relPoint, x=x, y=y }
end)
textFrame:Hide()

textFrame.bg = textFrame:CreateTexture(nil, "BACKGROUND")
textFrame.bg:SetAllPoints(true)
textFrame.bg:SetColorTexture(0,0,0,0)
textFrame.text = textFrame:CreateFontString(nil, "OVERLAY")
textFrame.text:SetPoint("CENTER")

local function safeFont()
    local db = DB()
    local font = STANDARD_TEXT_FONT
    if LSM and db.textFont and db.textFont ~= "" then
        local f = LSM:Fetch("font", db.textFont, true)
        if f then font = f end
    end
    return font
end

local function applyTextStyle()
    local db = DB(); if not db then return end
    if textFrame:GetParent() ~= UIParent then textFrame:SetParent(UIParent) end
    local font = safeFont()
    local outline = db.textOutline or "OUTLINE"
    local size = db.textSize or 48
    textFrame.text:SetFont(font, size, outline)
    textFrame.text:SetText(db.text or "立即打断！")
    local c = db.textColor or {r=1,g=0.2,b=0.2,a=1}
    local ta = (db.textAlpha ~= nil) and db.textAlpha or (c.a or 1)
    textFrame.text:SetTextColor(c.r or 1, c.g or 0.2, c.b or 0.2, ta)
    local bga = math.max(0, math.min(1, db.bgAlpha or 0))
    textFrame.bg:SetColorTexture(0,0,0,bga)
    local tp = db.textPoint or DEFAULTS.profile.textPoint
    local relFrame = _G[tp.rel] or UIParent
    textFrame:ClearAllPoints()
    textFrame:SetPoint(tp.point, relFrame, tp.relPoint, tp.x, tp.y)
end
ns.applyTextStyle = applyTextStyle

function ns.ShowPreview() ns._testActive = true; if UIFrameFadeRemoveFrame then UIFrameFadeRemoveFrame(textFrame) end; applyTextStyle(); textFrame:SetAlpha(1); textFrame:Show() end
function ns.HidePreview() ns._testActive = false; if UIFrameFadeRemoveFrame then UIFrameFadeRemoveFrame(textFrame) end; textFrame:Hide() end

local function showTextAlert()
    local db = DB(); if not (db and db.showText) then return end
    if UIFrameFadeRemoveFrame then UIFrameFadeRemoveFrame(textFrame) end
    applyTextStyle(); textFrame:SetAlpha(1); textFrame:Show()
    if db.testSticky and ns._testActive then return end
    local dur = math.max(1.0, math.min(3.0, db.textDuration or 2.0))
    C_Timer.After(dur, function()
        if not textFrame:IsShown() then return end
        if UIFrameFadeOut then UIFrameFadeOut(textFrame, 0.25, 1, 0) end
        C_Timer.After(0.3, function() if not ns._testActive then textFrame:Hide() end end)
    end)
end

-- ===== Sound =====
local function withTempChannelVolume(channel, desired, func)
    local db = DB()
    if not (db and db.useVolumeSlider) then return func() end
    local cvar = (channel == "Master" and "Sound_MasterVolume")
        or (channel == "SFX" and "Sound_SFXVolume")
        or (channel == "Dialog" and "Sound_DialogVolume")
        or (channel == "Music" and "Sound_MusicVolume")
        or (channel == "Ambience" and "Sound_AmbienceVolume")
    if not cvar then return func() end
    local old = tonumber(GetCVar(cvar)) or 1
    SetCVar(cvar, tostring(desired)); func(); C_Timer.After(0.1, function() SetCVar(cvar, tostring(old)) end)
end

local function playAlert()
    local db = DB(); if not (db and db.enabled) then return end
    local channel = db.volume or "Master"
    local chosen = db.sound or "RAID_WARNING"
    local function play()
        if type(chosen) == "string" and chosen:match("^LSM:") and LSM then
            local key = chosen:sub(5)
            local path = LSM:Fetch("sound", key, true)
            if path then PlaySoundFile(path, channel); return end
        end
        if chosen == "CUSTOM" and db.customPath and db.customPath ~= "" then
            PlaySoundFile(db.customPath, channel)
        else
            local kit = SOUND_MAP[chosen] or SOUNDKIT.RAID_WARNING
            PlaySound(kit, channel)
        end
    end
    local vol = math.max(0, math.min(1, db.volumeSlider or 1))
    withTempChannelVolume(channel, vol, play)
    showTextAlert()
end

-- ===== Debug helper =====
local function dprint(...)
  local db = DB()
  if db and (db.debug or db.debugAnnounce) then
    print("|cffff8800打断助手-调试模式：|r", ...)
  end
end

-- ===== Safe Spell text =====
local function SafeSpellText(spellId)
  if C_Spell and C_Spell.GetSpellLink then
    local link = C_Spell.GetSpellLink(spellId)
    if link then return link end
  end
  local name = GetSpellInfo and GetSpellInfo(spellId)
  if name then
    return ("|Hspell:%d|h[%s]|h"):format(spellId, name)
  end
  return "Spell "..tostring(spellId)
end

-- ===== Chat announcements =====
local lastAnnounced = {}
local INTERRUPT_SPELL_IDS = {
    [259]=1766,[260]=1766,[261]=1766, [62]=2139,[63]=2139,[64]=2139,
    [577]=183752,[581]=183752, [262]=57994,[263]=57994,[264]=57994,
    [71]=6552,[72]=6552,[73]=6552, [65]=96231,[66]=96231,[70]=96231,
    [1467]=351338,[1473]=351338, [268]=116705,[269]=116705,[270]=116705,
    [258]=15487,
}

local function myInterruptReady()
    if not DB().announceOnlyIfReady then return true end
    local spec = GetSpecialization()
    local id = INTERRUPT_SPELL_IDS[spec or 0]
    if not id then dprint("未知打断技能；已允许使用"); return true end
    local start, dur, enable = GetSpellCooldown(id)
    if enable == 0 then dprint("打断技能无法使用"); return false end
    local ready = (start == 0) or (start + dur - GetTime() <= 0.1)
    if not ready then dprint("打断技能正在冷却") end
    return ready
end

local function pickChannel()
  local ch = DB().announceChannel or "AUTO"
  if ch ~= "AUTO" then return ch end
  if IsInGroup(LE_PARTY_CATEGORY_INSTANCE) then return "INSTANCE_CHAT" end
  if IsInRaid() then return "RAID" end
  if IsInGroup() then return "PARTY" end
  return "SAY"
end

local function canAnnounceHere()
  if DB().announceInWorld then return true end
  local inInstance = IsInInstance()
  return inInstance
end

local function formatMsg(spellId, unit)
  local spell = SafeSpellText(spellId)
  local mob = UnitName(unit) or "mob"
  local t = DB().announceTemplate or "注意：%mob%正在施放%spell% - 请立即打断！"
  t = t:gsub("%%spell%%", tostring(spell))
  t = t:gsub("%%mob%%", tostring(mob))
  return t
end

local function maybeAnnounce(spellId, unit, castID)
  if not DB().announceEnabled then dprint("忽略：通报功能已关闭"); return end
  if not canAnnounceHere() then dprint("忽略：副本外通报功能已关闭"); return end
  if not myInterruptReady() then dprint("忽略：我的打断技能还未准备好"); return end
  local key = castID or (spellId..":"..(UnitGUID(unit) or ""))
  local now = GetTime()
  local last = lastAnnounced[key] or 0
  local thr = DB().announceThrottleS or 3
  if (now - last) < thr then dprint(("忽略：频率过快 (%.1fs < %.1fs)"):format(now-last, thr)); return end
  lastAnnounced[key] = now
  local ch = pickChannel()
  local msg = formatMsg(spellId, unit)
  dprint(("发送：%s -> %s"):format(msg, ch))
  SendChatMessage(msg, ch)
end

-- ===== Filters =====
local function scopeAllows()
    local db = DB()
    local scope = db and db.instanceScope or "ANY"
    if scope == "ANY" then return true end
    local inInstance, instanceType = IsInInstance()
    if not inInstance then return false end
    if scope == "INSTANCES" then
        return (instanceType == "party" or instanceType == "scenario" or instanceType == "raid")
    elseif scope == "RAID" then
        return instanceType == "raid"
    end
    return true
end

local function difficultyAllows()
    local db = DB()
    local name, instanceType, difficultyID = GetInstanceInfo()
    if not difficultyID then return true end
    if instanceType == "party" then
        local d = db.dungeonDiff or {}
        if difficultyID == 1 and d.N then return true end
        if difficultyID == 2 and d.H then return true end
        if difficultyID == 23 and d.M then return true end
        if difficultyID == 8 and d.MPLUS then return true end
        return false
    elseif instanceType == "raid" then
        local r = db.raidDiff or {}
        if difficultyID == 17 and r.LFR then return true end
        if difficultyID == 14 and r.N   then return true end
        if difficultyID == 15 and r.H   then return true end
        if difficultyID == 16 and r.M   then return true end
        return false
    end
    return true
end

local function isImportant(spellId)
    local db = DB()
    if spellId and db.ignoreSpells and db.ignoreSpells[spellId] then return false end
    if db.filterMode ~= "IMPORTANT" then return true end
    if spellId and db.importantSpells and db.importantSpells[spellId] then return true end
    if db.usePresetDungeon and PRESET_DUNGEON[spellId] then return true end
    if db.usePresetRaid and PRESET_RAID[spellId] then return true end
    return false
end

-- ===== Cast tracking =====
local seenCastIDs = {}
local function shouldWarnForUnit(unit)
    if not unit or not UnitExists(unit) then return false end
    if not UnitCanAttack("player", unit) then return false end
    if not UnitIsEnemy("player", unit) then return false end
    return true
end

local function checkCast(unit, event)
    if not shouldWarnForUnit(unit) then return end
    if not scopeAllows() then return end
    if not difficultyAllows() then return end

    local name, _, _, startTimeMS, endTimeMS, _, castID, notInterruptible, spellId
    if event == "UNIT_SPELLCAST_START" then
        name, _, _, startTimeMS, endTimeMS, _, castID, notInterruptible, spellId = UnitCastingInfo(unit)
    elseif event == "UNIT_SPELLCAST_CHANNEL_START" then
        name, _, _, startTimeMS, endTimeMS, _, notInterruptible, spellId = UnitChannelInfo(unit)
        castID = (name or "CHAN") .. ":" .. (startTimeMS or 0) .. ":" .. (unit or "x")
    else
        return
    end
    if not name or notInterruptible then return end
    if not isImportant(spellId) then return end
    if seenCastIDs[castID] then return end
    seenCastIDs[castID] = true
    playAlert()
    maybeAnnounce(spellId, unit, castID)
end

InterruptHelper.unitsWatched = {
    target = true, focus = true,
    boss1 = true, boss2 = true, boss3 = true, boss4 = true, boss5 = true,
    arena1 = true, arena2 = true, arena3 = true, arena4 = true, arena5 = true,
}
local function isWatchedUnit(unit)
    if InterruptHelper.unitsWatched[unit] then return true end
    if unit and unit:match("^nameplate") then return true end
    return false
end

-- ===== Slash =====
local function addSpell(id)
    id = tonumber(id); if not id then print("|cffff8800打断助手：|r 无效法术id"); return end
    DB().importantSpells[id] = true
    local n = GetSpellInfo(id) or ("Spell "..id)
    print("|cffff8800打断助手：|r 已添加重要法术：", n, "("..id..")")
end
local function removeSpell(id)
    id = tonumber(id); if not id then print("|cffff8800打断助手：|r 无效法术id"); return end
    DB().importantSpells[id] = nil
    local n = GetSpellInfo(id) or ("Spell "..id)
    print("|cffff8800打断助手：|r 已移除：", n, "("..id..")")
end
local function listSpells()
    print("|cffff8800打断助手重要法术列表：|r")
    local any=false
    for id,_ in pairs(DB().importantSpells) do
        local n = GetSpellInfo(id) or ("Spell "..id)
        print(" -", n, "("..id..")"); any=true
    end
    if not any then print(" (空)") end
end

InterruptHelper:SetScript("OnEvent", function(self, event, arg1)
    if event == "ADDON_LOADED" and arg1 == "InterruptHelper" then
        if AceDB then ns.db = AceDB:New("InterruptHelperDB", DEFAULTS, true) else _G.InterruptHelperDB = _G.InterruptHelperDB or DEFAULTS.profile end

        SLASH_INTERRUPTHELPER1 = "/ih"
        SLASH_INTERRUPTHELPER2 = "/interrupthelper"
        SlashCmdList["INTERRUPTHELPER"] = function(msg)
            msg = (msg or ""):lower()
            if msg == "toggle" or msg == "on" or msg == "off" then
                if msg == "on" then DB().enabled = true
                elseif msg == "off" then DB().enabled = false
                else DB().enabled = not DB().enabled end
                print("|cffff8800打断助手：|r 启用 =", DB().enabled and "开" or "关")
            elseif msg == "test" then
                ns._testActive = DB().testSticky
                local old = DB().filterMode; DB().filterMode = "ALL"
                playAlert(); DB().filterMode = old
            elseif msg == "unlock" then
                textFrame.locked = false; textFrame.bg:SetColorTexture(0,0,0,DB().bgAlpha or 0); textFrame:Show()
                ns._testActive = true
                print("|cffff8800打断助手：|r 文本框已解锁。拖动并放置好后然后输入 /ih 锁定。")
            elseif msg == "lock" then
                textFrame.locked = true; ns._testActive = false; textFrame:Hide()
                print("|cffff8800打断助手：|r 文本框已锁定。")
            elseif msg == "hidetest" then
                ns._testActive = false; textFrame:Hide()
            elseif msg == "debug" then
                DB().debugAnnounce = not DB().debugAnnounce
                print("|cffff8800打断助手：|r 聊天通报调试模式 =", DB().debugAnnounce and "开" or "关")
            elseif msg:match("^add%s+%d+") then
                addSpell(msg:match("^add%s+(%d+)"))
            elseif msg:match("^remove%s+%d+") then
                removeSpell(msg:match("^remove%s+(%d+)"))
            elseif msg == "list" then
                listSpells()
            else
                if ns and ns.OpenOptions then ns.OpenOptions() end
                print("|cffff8800打断助手|r命令提示：/ih add <id> | remove <id> | list | test | unlock | lock | debug")
            end
        end
    elseif event == "PLAYER_LOGIN" then
        SetCVar("nameplateShowEnemies", 1); ns.applyTextStyle()
    elseif event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START" then
        local unit = arg1; if isWatchedUnit(unit) then checkCast(unit, event) end
    elseif event == "PLAYER_REGEN_ENABLED" then
        wipe(seenCastIDs)
    end
end)

InterruptHelper:RegisterEvent("ADDON_LOADED")
InterruptHelper:RegisterEvent("PLAYER_LOGIN")
InterruptHelper:RegisterEvent("UNIT_SPELLCAST_START")
InterruptHelper:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
InterruptHelper:RegisterEvent("PLAYER_REGEN_ENABLED")
